/*
 *  engine.cpp
 *  
 *  Created by James Dykstra
 *	Team 5
 *
 *  Copyright 2010 Michigan Technological University. All rights reserved.
 */
#include "engine.h"

int netThread(void *data);
SDL_mutex *nlock = NULL;
SDL_mutex *olock = NULL;

GameEngine::GameEngine(char *ip)
{
  renderer = new Renderer();
  nlock = SDL_CreateMutex();
  olock = SDL_CreateMutex();
  net = new Network(ip);
	map= new Map("map2",renderer);
	logic = new Logic(renderer, map);
  sound = new Sound();
  player = new PlayerControlledShip(renderer, logic);
  for(int i=0; i<MAX_CLIENTS; i++)
	  opponents[i] = new Opponent(renderer);

  player->setMapWidth(map->getMapWidth());
  player->setMapHeight(map->getMapHeight());
  
  net->setPlayer(player);
  net->setOpponents(opponents);
  net->setSound(sound);
  net->open();
	
  if(!net->isServer())
	  net->send("helo", 0, false);
  else
  {
	  for(int i=0; i<MAX_CLIENTS; i++)
		  opponents[i]->setClientNum((i+1));
  }

  sthread = SDL_CreateThread(netThread, net);
  
  lastPos.x = -1;
  lastPos.y = -1;
  lastAngle = -1;

  delta = new Timer();
  fps = new Timer();
  running = true;
  
  delta->start();
}
GameEngine::~GameEngine()
{
  if(sthread!=NULL)
    SDL_KillThread(sthread);
  if(nlock != NULL)
	SDL_DestroyMutex(nlock);
  if(olock != NULL)
	SDL_DestroyMutex(olock);
  if(net!=NULL)
    delete net;
  for(int i=0; i<MAX_CLIENTS; i++)
	delete opponents[i];
  if(player!=NULL)
    delete player;
  if(sound != NULL)
	delete sound;
  if(logic != NULL)
    delete logic;
  if(fps != NULL)
    delete fps;
  if(delta != NULL)
    delete delta;
  if(map != NULL)
    delete map;
  if(renderer != NULL)
    delete renderer;
}
void GameEngine::run()
{
  sound->playSound(SND_AMB,-1);
  while(running)
  {
	//Update positions of sprites and objects
	update();
	//Draw(in order): map, objects, enemies, players
	draw();
	//Handle input
	handleInput();
	//Send any potentially new data over network
	netUpdate();
	//Cap frame rate
	capFrameRate();
  }
}
void GameEngine::netUpdate()
{
	if(net->connected() && net->getClientNum() != -1)
	{
		std::string buf = "";
		if(lastPos.x != player->getRect()->x)
		{
			buf += "X:";
			buf += Network::ProtocolParser::numToStr(player->getRect()->x);
			lastPos.x = player->getRect()->x;
		}
		if(lastPos.y != player->getRect()->y)
		{
			buf += "Y:";
			buf += Network::ProtocolParser::numToStr(player->getRect()->y);
			lastPos.y = player->getRect()->y;
		}
		if(lastAngle != player->getAngle())
		{
			buf += "A:";
			buf += Network::ProtocolParser::numToStr(player->getAngle());
			lastAngle = player->getAngle();
		}
		if(!buf.empty())
		{
			if(net->isServer())
				net->sendAll(buf, -1, true);
			else
				net->send(buf);
		}
	}
}
void GameEngine::update()
{
	fps->start();
	
	if(player->getHP()>0)
		player->moveFighter(delta->getTicks());

	if(net->connected())
	{
		for(int i=0; i<MAX_CLIENTS; i++)
		{
			if(opponents[i]->getClientNum() != -1)
			{
				SDL_mutexP(olock);
				opponents[i]->moveFighter(delta->getTicks());
				SDL_mutexV(olock);
				if(opponents[i]->getHP() > 0)
				{
					if (!player->isInMercyMode()) 
					{
						SDL_mutexP(olock);
						if(logic->checkCollide(player, opponents[i]))
						{
							SDL_mutexV(olock);
							sound->playSound(SND_EXPLOS, 0);
							player->dmg(opponents[i]->getMaxHP()/10);
							player->setMercy(50);
						}
						else
							SDL_mutexV(olock);
					}
				}
				for(int b=0; b<BEAM_LIMIT; b++)
				{
					if(opponents[i]->getBeam(b) != NULL)
					{
						if(opponents[i]->getBeam(b)->isActive() && player->getHP()>0 && !player->isInMercyMode())
						{
							SDL_mutexP(olock);
							if(logic->checkCollide(player, opponents[i]->getBeam(b))) {
								SDL_mutexV(olock);
								std::string tmp = "H:";
								tmp += Network::ProtocolParser::numToStr(opponents[i]->getBeam(b)->getID());
								net->send(tmp);
								sound->playSound(SND_EXPLOS, 0);
								player->dmg(opponents[i]->getBeam(b)->getDamage());
								player->setMercy(50);
								opponents[i]->getBeam(b)->setActive(false);
							}
							else
								SDL_mutexV(olock);
						}
					}
				}
			}
		}
	}
	//Restart elapsed ticks timer
	delta->start();
}
void GameEngine::draw()
{

	//the x and y coordinates of the upper left corner of the screen on the map
	int mapDrawX=0;
	int mapDrawY=0;

	if(map!=NULL){
		if(player->getRect()->x+(player->getRect()->w/2)<(renderer->getScreenWidth()/2)){
			mapDrawX=0;
		}
		else if(player->getRect()->x+(player->getRect()->w/2)>(map->getMapWidth()-(renderer->getScreenWidth()/2))){
			mapDrawX=map->getMapWidth()-renderer->getScreenWidth();
		}
		else{
			mapDrawX=player->getRect()->x+(player->getRect()->w/2)-(renderer->getScreenWidth()/2);
		}
		if(player->getRect()->y+(player->getRect()->h/2)<(renderer->getScreenHeight()/2)){
			mapDrawY=0;
		}
		else if(player->getRect()->y+(player->getRect()->h/2)>(map->getMapHeight()-(renderer->getScreenHeight()/2))){
			mapDrawY=map->getMapHeight()-renderer->getScreenHeight();
		}
		else{
			mapDrawY=player->getRect()->y+(player->getRect()->h/2)-(renderer->getScreenHeight()/2);
		}
		map->drawMap(mapDrawX,mapDrawY);
	}

	/*
	  ALL_OBJECTS->show();
	  ALL_ENEMIES->show();
	  
	*/
	if(net->connected())
	{
		for(int i=0; i<MAX_CLIENTS; i++)
		{
			if(opponents[i]->getHP() > 0 && opponents[i]->getClientNum() != -1)
			{
				SDL_mutexP(olock);
				opponents[i]->show(mapDrawX,mapDrawY);
				SDL_mutexV(olock);
				/*for(int b=0; b<BEAM_LIMIT; b++)
				{
					SDL_mutexP(olock);
					if(opponents[i]->getBeam(b) != NULL)
					{
						opponents[i]->getBeam(b)->show(mapDrawX, mapDrawY);
					}
					SDL_mutexV(olock);
				}
				*/
			}
		}
	}

	player->show(mapDrawX,mapDrawY);
	renderer->update();
}
bool GameEngine::handleInput(){


  //update the keyevent variable
  while(SDL_PollEvent(&keyevent))
  {

    switch(keyevent.type){
	case SDL_QUIT:
		running = false;
		break;
        case SDL_KEYDOWN:
            switch(keyevent.key.keysym.sym){
                case SDLK_LEFT:
			player->turnLeft();
                    	break;
                case SDLK_RIGHT:
			player->turnRight();
                    	break;
                case SDLK_UP:
			player->accelerateForward();
                    	break;
                case SDLK_DOWN:
			player->accelerateBackward();
			break;
		case SDLK_z:
			if (player->getNormalAttack()->fire())
			{
				sound->playSound(SND_LASER,0);
				if(net->connected())
				{
					if(net->isServer())
						net->sendAll("F:-2", -1, true);
					else
						net->send("F:-2");
				}
			}
			break;
		case SDLK_x:
			if (player->getWideAttack()->fire())
			{
				sound->playSound(SND_LASER,0);
				if(net->connected())
				{
					if(net->isServer())
						net->sendAll("F:-3", -1, true);
					else
						net->send("F:-3");
				}
			}
			break;
		case SDLK_c:
			if (player->getSpecialAttack()->fire())
			{
				sound->playSound(SND_LASER,0);
				if(net->connected())
				{
					if(net->isServer())
						net->sendAll("F:-4", -1, true);
					else
						net->send("F:-4");
				}
			}
			break;
		case SDLK_ESCAPE:
			running = false;
			if(net->connected())
			{
				if(net->isServer())
					net->sendAll("quit", -1, false);
				else
				{
					std::string goodbye = "Q:";
					goodbye += Network::ProtocolParser::numToStr(net->getClientNum());
					net->send(goodbye, 0, false);
				}
			}
			break;
                default:
                    break;
            }
            break;
        case SDL_KEYUP:
            switch(keyevent.key.keysym.sym){
                case SDLK_LEFT:
                case SDLK_RIGHT:
					player->stopTurn();
                    break;
                case SDLK_UP:
                case SDLK_DOWN:
					player->brake();
                    break;
                default:
                    break;
            }

    }
    return true;
  }
 
  return true;

}
void GameEngine::capFrameRate()
{
	//If there is time to spare after processing/drawing the frame, regulate.
	if(fps->getTicks()<(1000/FPS))
		SDL_Delay((1000/FPS) - fps->getTicks());
}

/**
 * Read settings from the settings.xml file. If the setting does not exist,
 * it returns NULL.
 * @param settingname name of the setting whose value will be read.
 * @returns A string representing the value of the setting
 */
const char *   GameEngine::settingRead( string settingname ) {
  TiXmlDocument doc( "settings.xml" );
  if( !doc.LoadFile() ) {
    fprintf( stderr, "err: settings file failed to load with error %s!\n", doc.ErrorDesc() );
  }

  TiXmlHandle hDoc(&doc);

  TiXmlElement* pElem = hDoc.FirstChildElement( settingname.c_str() ).Element();
  if (!pElem) {
    return NULL;
  }
  return pElem->GetText();

}

/**
 * Write to settings.xml file.  If the setting exists, it is overwritten.
 * The first parameter is the setting that is to be created/changed, and the 
 * second is the value for the setting.
 * @settingname name of the element that is the setting
 * @settingvalue value of the element that is the setting
 */
void     GameEngine::settingWrite( string settingname, string settingvalue ) {

  TiXmlDocument doc( "settings.xml" );

  //open a handler for easier navigation of entries
  TiXmlHandle hDoc(&doc);

  if( !doc.LoadFile() ) {
    fprintf( stderr, "err: settings file failed to load with error %s!\n", doc.ErrorDesc() );
    doc.ClearError();
  }


  //grab the element for the listed setting
  TiXmlElement* pElem = hDoc.FirstChildElement( settingname.c_str() ).Element();

  //if it exists, clear it, and recreate it
  if ( pElem ) {
    doc.RemoveChild( pElem );
  }

  //create the new element
  pElem = new TiXmlElement( settingname.c_str() );


  //add it to the opened document
  doc.LinkEndChild( pElem );


  //replace / insert new value into the new element
  pElem->LinkEndChild( new TiXmlText( settingvalue.c_str() ) );

  //save the settings
  doc.SaveFile();


}

//netThread
//
//Run in its own thread.
//Given a pointer to a valid network object (data)
//Using the network object this thread watches for data being sent.
//For testing/demo purposes all received data is written to recv.txt
int netThread(void *data)
{
	Network *net = (Network*)data;
	std::string buffer = "";
	Opponent *controlled;
	std::fstream in("recv.txt", std::fstream::out | std::fstream::trunc);
	bool run = true;
	while(run)
	{
		SDL_mutexP(nlock);
		UDPpacket *p = net->recvPacket();
		buffer = net->extractData(p);
		SDL_mutexV(nlock);
		if(buffer.length()>0)
		{
			in<<buffer.c_str()<<std::endl;

			if(strcmp(buffer.c_str(), "helo")==0)
			{
				if(net->isServer())
				{
					std::fstream log("log.txt", std::fstream::out | std::fstream::app);
					SDL_mutexP(nlock);
					if(net->newPlayerConnected(p))
						log<<"Client #"<<net->getCur()<<": Connected."<<std::endl;
					SDL_mutexV(nlock);
					log.close();
				}
			}
			else
			{
				int num = Network::ProtocolParser::extractiVal(buffer, 'N');
				if(num != -1)
				{
					SDL_mutexP(nlock);
					net->setClientNum(num);
					SDL_mutexV(nlock);
					switch(num)
					{
					case 1:
						net->getPlayer()->setPos((RES_WIDTH - net->getPlayer()->getRect()->w), net->getPlayer()->getRect()->h);
						break;
					case 2:
						net->getPlayer()->setPos(net->getPlayer()->getRect()->w, (RES_HEIGHT - net->getPlayer()->getRect()->h));
						break;
					case 3:
						net->getPlayer()->setPos((RES_WIDTH - net->getPlayer()->getRect()->w), (RES_HEIGHT - net->getPlayer()->getRect()->h));
						break;
					case 4:
						net->getPlayer()->setPos(net->getPlayer()->getRect()->w, net->getPlayer()->getRect()->h);
						break;
					default:
						break;
					}
					if(!net->isServer())
					{
						std::string reqUp = "U:";
						reqUp += Network::ProtocolParser::numToStr(net->getClientNum());
						net->send(reqUp, 0, false);
					}
				}
				else
				{
					int  q = Network::ProtocolParser::extractiVal(buffer, 'Q');
					int u = Network::ProtocolParser::extractiVal(buffer, 'U');
					if(q != -1)
					{
						if(net->isServer())
							net->sendAll(buffer, q, false);

						SDL_mutexP(nlock);
						net->removeClient(q);
						SDL_mutexV(nlock);
					}
					else if(u != -1)
					{
						SDL_mutexP(nlock);
						net->updateOpponents(u);
						SDL_mutexV(nlock);
					}
					else
					{
						int cNum = Network::ProtocolParser::extractiVal(buffer, 'P');
						if((cNum != net->getClientNum()))
						{
							if(net->isServer())
								net->sendAll(buffer, -1, false);
					
							controlled = net->getOpponent(cNum);

							if(!net->isServer())
							{
								if(controlled->getClientNum() != cNum)
								{
									SDL_mutexP(olock);
									controlled->setClientNum(cNum);
									controlled->setHP(100);
									SDL_mutexV(olock);

									std::fstream log("log.txt", std::fstream::out | std::fstream::app);
									log<<"Client #"<<net->getCur()<<": Joined."<<std::endl;
									log.close();
								}
							}

							int f = Network::ProtocolParser::extractiVal(buffer, 'F');
							if(f != -1)
							{
								SDL_mutexP(olock);

								controlled->action(f);
								SDL_mutexV(olock);
							}
							else
							{
								int val = Network::ProtocolParser::extractiVal(buffer, 'H');
								if(val>=0 && val<BEAM_LIMIT)
								{
									if(controlled != NULL)
									{
										if(controlled->getHP()>0)
										{
											if(net->getPlayer()->getBeam(val) != NULL)
											{
												SDL_mutexP(olock);
												controlled->dmg(net->getPlayer()->getBeam(val)->getDamage());
												SDL_mutexV(olock);
												net->getPlayer()->getBeam(val)->setActive(false);
												net->getSound()->playSound(SND_EXPLOS, 0);
											}
											SDL_mutexP(olock);
											controlled->action(val);
											SDL_mutexV(olock);
										}
									}
								}

								val = Network::ProtocolParser::extractiVal(buffer, 'X');
								if(val != -1)
								{
									if(controlled != NULL)
									{
										SDL_mutexP(olock);
										controlled->changeX(val);
										SDL_mutexV(olock);
									}
								}

								val = Network::ProtocolParser::extractiVal(buffer, 'Y');
								if(val != -1)
								{
									if(controlled != NULL)
									{
										SDL_mutexP(olock);
										controlled->changeY(val);
										SDL_mutexV(olock);
									}
								}

								float v = Network::ProtocolParser::extractfVal(buffer, 'A');
								if(v != -1)
								{
									if(controlled != NULL)
									{
										SDL_mutexP(olock);
										controlled->changeA(v);
										SDL_mutexV(olock);
									}
								}
							}
						}
					}
				}
			}
		}
		else
			SDLNet_FreePacket(p);
	}

	in.close();
	SDL_mutexP(nlock);
	net->close();
	SDL_mutexV(nlock);
	
	return 0;
}
